package analyses;

import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.util.Collections;
import java.util.Comparator;
import java.util.Vector;

import simpleGeom.Vec2D;

public class ViewAnalyserLine {

	Vector<Segment> segmentCollection;

	Vector eventCollection;

	Vector<Point2D> viewPtCollection;

	EventComparator eventComparator = new EventComparator();

	Segment baseSegment;

	Segment viewMaxSegment;

	Point2D p1Rotiert = new Point2D.Double();

	Point2D p2Rotiert = new Point2D.Double();

	static int cutPoint = 0;

	static int startPoint = -1;

	static int endPoint = 1;

	public Vector<Point2D> getViewPoly(Point2D p1, Point2D p2,
			Vector<Point2D> linePoints, double maxView) {
		System.out.println("***Analyse");
		segmentCollection = new Vector<Segment>();
		eventCollection = new Vector();

		Point2D v = Vec2D.sub(p2, p1);
		// v.getAngleAtan2()
		double rot = Vec2D.getTheta(v);
		AffineTransform t = new AffineTransform();
		t.rotate(-rot);

		Line2D cLine;

		t.transform(p1, p1Rotiert);
		t.transform(p2, p2Rotiert);

		for (int i = 0; i < linePoints.size(); i += 2) {
			Point2D lineP1 = linePoints.get(i);
			Point2D lineP2 = linePoints.get(i + 1);
			Point2D p1rot = new Point2D.Double();
			Point2D p2rot = new Point2D.Double();

			t.transform(lineP1, p1rot);
			t.transform(lineP2, p2rot);

			Segment cSegment = new Segment(p1rot, p2rot);

			cSegment = (Segment) this.sortSegX(cSegment);
			if (lineP1.equals(p1) && lineP2.equals(p2)) {
				baseSegment = cSegment;
				createSegmentEvents(baseSegment);
			} else {
				createSegmentEvents(cSegment);
			}

		}

		viewMaxSegment = new Segment(p1Rotiert.getX(), p1Rotiert.getY()
				+ maxView, p2Rotiert.getX(), p1Rotiert.getY() + maxView);
		viewMaxSegment = (Segment) this.sortSegX(viewMaxSegment);
		createSegmentEvents(viewMaxSegment);

		Collections.sort(eventCollection, eventComparator);

		Vector<Point2D> rotViewCollection = new Vector<Point2D>();

		viewPtCollection = new Vector<Point2D>();

		loopEvents();

		// Vector<Vector2D> rotViewCollection = new Vector<Vector2D>();
		for (int i = 0; i < viewPtCollection.size(); i++) {

			Point2D cPos = viewPtCollection.get(i);

			Point2D cProt = new Point2D.Double();
			try {
				t.inverseTransform(cPos, cProt);
			} catch (NoninvertibleTransformException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			rotViewCollection.add(cProt);
		}

		return rotViewCollection;
	}

	@SuppressWarnings("unchecked")
	public Vector<Point2D> getHEdgeView(Point2D p1, Point2D p2,
			Vector<Line2D> lineCollection) {
		// System.out.println("***Analyse");
		segmentCollection = new Vector<Segment>();
		eventCollection = new Vector();

		Point2D v = Vec2D.sub(p2, p1);
		// v.getAngleAtan2()
		double rot = Vec2D.getTheta(v);
		AffineTransform t = new AffineTransform();
		t.rotate(-rot);

		Line2D cLine;

		t.transform(p1, p1Rotiert);
		t.transform(p2, p2Rotiert);

		for (int i = 0; i < lineCollection.size(); i++) {
			cLine = lineCollection.get(i);
			Point2D p1rot = new Point2D.Double();
			Point2D p2rot = new Point2D.Double();

			t.transform(cLine.getP1(), p1rot);
			t.transform(cLine.getP2(), p2rot);

			Segment cSegment = new Segment(p1rot, p2rot);

			cSegment = (Segment) this.sortSegX(cSegment);
			if (cLine.getP1().equals(p1) && cLine.getP2().equals(p2)) {
				baseSegment = cSegment;
				createSegmentEvents(baseSegment);
			} else {
				createSegmentEvents(cSegment);
			}

		}

		double maxView = 100;
		viewMaxSegment = new Segment(p1Rotiert.getX(), p1Rotiert.getY()
				+ maxView, p2Rotiert.getX(), p1Rotiert.getY() + maxView);
		viewMaxSegment = (Segment) this.sortSegX(viewMaxSegment);
		createSegmentEvents(viewMaxSegment);

		Collections.sort(eventCollection, eventComparator);

		Vector<Point2D> rotViewCollection = new Vector<Point2D>();

		viewPtCollection = new Vector<Point2D>();

		loopEvents();
		// System.out.println("viewPtCollection: "+viewPtCollection.size());
		// Vector<Vector2D> rotViewCollection = new Vector<Vector2D>();
		for (int i = 0; i < viewPtCollection.size(); i++) {

			Point2D cPos = viewPtCollection.get(i);

			Point2D cProt = new Point2D.Double();
			try {
				t.inverseTransform(cPos, cProt);
			} catch (NoninvertibleTransformException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			rotViewCollection.add(cProt);
		}

		return rotViewCollection;
	}

	private void loopEvents() {

		SweepEvent cEvent = (SweepEvent) eventCollection.firstElement();

		boolean isStart = false;

		while (cEvent != null) {

			Segment cDownSeg = null;
			Segment lastDownSeg = null;
			int indexOverBase = segmentCollection.indexOf(baseSegment);

			if (isStart && indexOverBase >= 0) {
				indexOverBase++;
				if (indexOverBase < segmentCollection.size()) {
					lastDownSeg = segmentCollection.get(indexOverBase);
				}
			}

			double cEventX = cEvent.getX();
			if (cEvent.getType() == startPoint) {

				Segment cSeg = (Segment) cEvent.getCSeg();
				if (segmentCollection.size() > 0) {
					int insertIndex = Collections.binarySearch(
							segmentCollection, cSeg, new SegmentXYComparator(
									cEvent.getX()));
					if (insertIndex < 0) {
						// nicht enthalten
						insertIndex = Math.abs(insertIndex) - 1;
					}
					segmentCollection.insertElementAt(cSeg, insertIndex);
					int beforeIndex = insertIndex - 1;
					if (beforeIndex >= 0) {
						Segment beforeSeg = segmentCollection.get(beforeIndex);
						testCrossPoint(beforeSeg, cSeg, cEventX);
					}
					int afterIndex = insertIndex + 1;
					if (afterIndex < segmentCollection.size()) {
						Segment afterSeg = segmentCollection.get(afterIndex);
						testCrossPoint(cSeg, afterSeg, cEventX);
					}

				} else {
					segmentCollection.add(cSeg);
				}

			} else if (cEvent.getType() == endPoint) {

				if (segmentCollection.size() > 0) {
					Segment cSeg = (Segment) cEvent.getCSeg();

					int insertIndex = segmentCollection.indexOf(cSeg);
					// System.out.println(insertIndex);
					segmentCollection.remove(insertIndex);
					int beforeIndex = insertIndex - 1;
					int afterIndex = insertIndex;

					if (beforeIndex >= 0
							&& afterIndex < segmentCollection.size()) {
						Segment beforeSeg = segmentCollection.get(beforeIndex);
						Segment afterSeg = segmentCollection.get(afterIndex);
						testCrossPoint(beforeSeg, afterSeg, cEventX);
					}
				}

			} else if (cEvent.getType() == cutPoint) {

				Segment lowSeg = (Segment) cEvent.getLowSeg();
				Segment upSeg = (Segment) cEvent.getUpSeg();
				int lowIndex = segmentCollection.indexOf(lowSeg);
				int upIndex = segmentCollection.indexOf(upSeg);

				if (upIndex < lowIndex) {
					int swap = upIndex;
					upIndex = lowIndex;
					lowIndex = swap;
				}

				Collections.swap(segmentCollection, lowIndex, upIndex);
				int beforeIndex = lowIndex - 1;
				int afterIndex = upIndex + 1;
				if (beforeIndex >= 0) {
					testCrossPoint(segmentCollection.get(beforeIndex),
							segmentCollection.get(lowIndex), cEventX);
				}
				if (afterIndex < segmentCollection.size()) {
					testCrossPoint(segmentCollection.get(upIndex),
							segmentCollection.get(afterIndex), cEventX);
				}

			}
			cDownSeg = null;

			if (cEvent.getType() == startPoint
					&& cEvent.getCSeg().equals(viewMaxSegment)) {
				isStart = true;

			}

			indexOverBase = segmentCollection.indexOf(baseSegment);
			if (isStart && indexOverBase >= 0) {
				indexOverBase++;
				if (indexOverBase < segmentCollection.size()) {
					cDownSeg = segmentCollection.get(indexOverBase);
				}
			}

			if (isStart && cDownSeg != lastDownSeg) {
				Point2D vP1;
				Point2D vP2;

				if (lastDownSeg != null) {
					vP1 = new Point2D.Double(cEvent.getX(),
							lastDownSeg.getYatX(cEvent.getX()));
					viewPtCollection.add(vP1);
				}

				if (cDownSeg != null) {
					vP2 = new Point2D.Double(cEvent.getX(),
							cDownSeg.getYatX(cEvent.getX()));
					// if (!vP2.equals(vP1)) {
					if (cEvent.getType() != cutPoint
							|| cEvent.lowSeg != lastDownSeg) {
						viewPtCollection.add(new Point2D.Double(cEvent.getX(),
								cDownSeg.getYatX(cEvent.getX())));
					}
				}
			}

			eventCollection.remove(0);
			if (eventCollection.size() > 0) {
				cEvent = (SweepEvent) eventCollection.firstElement();
			} else {
				cEvent = null;
			}

		}

	}

	private Segment sortSegX(Segment cLine) {
		if (cLine.getX1() > cLine.getX2()) {
			Point2D swapP = cLine.getPos2();
			cLine.setPos2(cLine.getPos1());
			cLine.setPos1(swapP);
		}
		return cLine;
	}

	// avoid double events
	private void createSegmentEvents(Segment segment) {
		int eventType1 = startPoint;
		int eventType2 = endPoint;
		if (segment.getX1() > segment.getX2()) {
			eventType1 = endPoint;
			eventType2 = startPoint;
		}
		SweepEvent cEvent1 = new SweepEvent(eventType1, segment,
				segment.getX1(), segment.getY1());
		eventCollection.add(cEvent1);
		SweepEvent cEvent2 = new SweepEvent(eventType2, segment,
				segment.getX2(), segment.getY2());
		eventCollection.add(cEvent2);
	}

	private void testCrossPoint(Segment seg1, Segment seg2, double cX) {
		Vec2D v = Vec2D.segmentSegmentIntersection(seg1.getPos1(),
				seg1.getPos2(), seg2.getPos1(), seg2.getPos2());
		Point2D crossPoint = new Point2D.Double(v.x, v.y);

		if (crossPoint != null && crossPoint.getX() > cX) {
			SweepEvent newEvent = new SweepEvent(cutPoint, seg1, seg2,
					crossPoint.getX(), crossPoint.getY());
			if (eventCollection.size() > 0) {
				int insertIndex = Collections.binarySearch(eventCollection,
						newEvent, eventComparator);

				if (insertIndex < 0) {

					insertIndex = Math.abs(insertIndex) - 1;
					eventCollection.insertElementAt(newEvent, insertIndex);
				}

			}
		}
	}

	public class SegmentXYComparator implements Comparator {
		double cX;

		public SegmentXYComparator(double cX) {
			this.cX = cX;
		}

		public int compare(Object line1, Object line2) {
			Segment seg1 = (Segment) line1;
			Segment seg2 = (Segment) line2;
			double y1 = seg1.getYatX(cX);
			double y2 = seg2.getYatX(cX);
			if (y1 < y2)
				return -1;
			if (y1 > y2)
				return 1;
			if (seg1.getY1() < seg2.getY1())
				return -1;
			if (seg1.getY1() > seg2.getY1())
				return 1;
			return 0;
		}
	}

	public class Segment {
		Point2D p1;
		Point2D p2;
		double m = 0;
		double c = 0;

		public Segment(Point2D arg0, Point2D arg1) {
			p1 = arg0;
			p2 = arg1;
			m = (p2.getY() - p1.getY()) / (p2.getX() - p1.getX());
			c = p1.getY() - m * p1.getX();
		}

		public Segment(double x1, double y1, double x2, double y2) {
			this(new Point2D.Double(x1, y1), new Point2D.Double(x2, y2));
		}

		public double getYatX(double x) {
			return x * m + c;
		}

		public Line2D getLine2D() {
			return new Line2D.Double(p1, p2);
		}

		public Point2D getPos1() {
			// TODO Auto-generated method stub
			return p1;
		}

		public Point2D getPos2() {
			// TODO Auto-generated method stub
			return p2;
		}

		public void setPos1(Point2D p1) {
			// TODO Auto-generated method stub
			this.p1 = p1;
		}

		public void setPos2(Point2D p2) {
			// TODO Auto-generated method stub
			this.p2 = p2;
		}

		public double getX1() {
			// TODO Auto-generated method stub
			return getPos1().getX();
		}

		public double getX2() {
			// TODO Auto-generated method stub
			return getPos2().getX();
		}

		public double getY1() {
			// TODO Auto-generated method stub
			return getPos1().getY();
		}

		public double getY2() {
			// TODO Auto-generated method stub
			return getPos2().getY();
		}

		public Point2D getPos() {
			// TODO Auto-generated method stub
			return this.getPos1();
		}

		public void setPos(Point2D cPos) {
			// TODO Auto-generated method stub
			this.setPos(cPos);
		}

	}

	public class SweepEvent extends Point2D.Double {

		int type;
		Segment lowSeg;
		Segment upSeg;
		Segment cSeg;

		public SweepEvent(int type, Segment cSeg, double x, double y) {
			super(x, y);
			this.setType(type);
			this.setCSeg(cSeg);
		}

		public SweepEvent(int type, Segment lowSeg, Segment upSeg, double x,
				double y) {
			super(x, y);
			this.setType(type);
			this.setLowSeg(lowSeg);
			this.setUpSeg(upSeg);
		}

		public Segment getCSeg() {
			return cSeg;
		}

		public void setCSeg(Segment seg) {
			cSeg = seg;
		}

		public Segment getLowSeg() {
			return lowSeg;
		}

		public void setLowSeg(Segment lowSeg) {
			this.lowSeg = lowSeg;
		}

		public int getType() {
			return type;
		}

		public void setType(int type) {
			this.type = type;
		}

		public Segment getUpSeg() {
			return upSeg;
		}

		public void setUpSeg(Segment upSeg) {
			this.upSeg = upSeg;
		}

	}

	public class EventComparator implements Comparator {

		public int compare(Object line1, Object line2) {

			SweepEvent e1 = (SweepEvent) line1;
			SweepEvent e2 = (SweepEvent) line2;

			if (e1.x < e2.x)
				return -1;
			if (e1.x > e2.x)
				return 1;
			if (e1.y < e2.y)
				return -1;
			if (e1.y > e2.y)
				return 1;
			if (e1.x == e2.x && e1.getType() < e2.getType())
				return -1;
			if (e1.x == e2.x && e1.getType() > e2.getType())
				return 1;
			if (e1.getType() == cutPoint && e1.getType() == e2.getType()
					&& e1.upSeg == e2.upSeg && e1.lowSeg == e2.lowSeg)
				return 0;
			if (e1.getType() == cutPoint && e1.getType() == e2.getType()
					&& e1.upSeg == e2.lowSeg && e1.lowSeg == e2.upSeg)
				return 0;
			if ((e1.getType() == endPoint || e1.getType() == startPoint)
					&& e1.getType() == e2.getType() && e1.cSeg == e2.cSeg)
				return 0;
			return 0;
		}

	}

}
